Skip to contentMethod: lambda$gameOver$0(IC4Player, IC4Solution)
      1: package de.fhdw.gaming.ipspiel23.c4.domain.impl;
2: 
3: import java.util.Arrays;
4: import java.util.Collections;
5: import java.util.HashMap;
6: import java.util.Map;
7: import java.util.Objects;
8: import java.util.Optional;
9: import java.util.Set;
10: 
11: import de.fhdw.gaming.core.domain.PlayerState;
12: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Board;
13: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Player;
14: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Solution;
15: import de.fhdw.gaming.ipspiel23.c4.domain.IC4State;
16: 
17: /**
18:  * The default implementation of the {@link IC4State} interface.
19:  */
20: public class C4State implements IC4State {
21: 
22:     /**
23:      * The board of the Connect Four game.
24:      */
25:     private final IC4Board board;
26: 
27:     /**
28:      * The players of the Connect Four game, ordered by their respective tokens such that 
29:      * {@code index == players[index].getToken() - 1 }
30:      * holds true for all players.
31:      */
32:     private final IC4Player[] players;
33: 
34:     /**
35:      * The index of the current player in the {@link #players} array.
36:      */
37:     private int currentPlayerIndex;
38: 
39:     /**
40:      * The {@link Map} of players, mapping the player names to the respective player instances.
41:      */
42:     private Map<String, IC4Player> playerMap;
43: 
44:     /**
45:      * Constructs a new {@link C4State} instance.
46:      * @param board The board of the Connect Four game.
47:      * @param players The players of the Connect Four game, ordered by their respective tokens such that
48:      * {@code index == players[index].getToken() - 1 }
49:      * holds true for all players.
50:      * @throws NullPointerException If {@code board} or {@code players} is {@code null}.
51:      * @throws IllegalArgumentException If the {@code players} array is not ordered by player tokens ascending,
52:      * starting at token 1 and incrementing by 1 for each following player.
53:      */
54:     public C4State(final IC4Player[] players, final IC4Board board) {
55:         this.board = Objects.requireNonNull(board, "board");
56:         this.players = Objects.requireNonNull(players, "players");
57:         for (int i = 0; i < players.length; i++) {
58:             if (players[i].getToken() != i + 1) {
59:                 throw new IllegalArgumentException(String.format("The 'players' array must be ordered by player "
60:                     + "tokens ascending, starting at token 1 and incrementing by 1 for each following player!. "
61:                     + "Player %s at index %s violates this rule!", 
62:                     players[i], i));
63:             }
64:         }
65:     }
66: 
67:     /**
68:      * Constructs a new {@link C4State} instance by copying the given {@code source} state.
69:      * @param source The source state to copy.
70:      */
71:     private C4State(final C4State source) {
72:         this.board = source.board.deepCopy();
73:         this.players = new IC4Player[source.players.length];
74:         this.currentPlayerIndex = source.currentPlayerIndex;
75:         for (int i = 0; i < this.players.length; i++) {
76:             this.players[i] = source.players[i].deepCopy();
77:         }
78:     }
79: 
80:     @Override
81:     public Map<String, IC4Player> getPlayers() {
82:         if (this.playerMap == null) {
83:             final HashMap<String, IC4Player> localPlayerMap = new HashMap<>(players.length);
84:             for (final IC4Player player : players) {
85:                 localPlayerMap.put(player.getName(), player);
86:             }
87:             this.playerMap = localPlayerMap;
88:         }
89:         return this.playerMap;
90:     }
91: 
92:     @Override
93:     public Set<IC4Player> computeNextPlayers() {
94:         final IC4Player currentPlayer = this.getCurrentPlayer();
95:         if (currentPlayer.getState().equals(PlayerState.PLAYING)) {
96:             return Collections.singleton(currentPlayer);
97:         }
98:         return Collections.emptySet();
99:     }
100: 
101:     @Override
102:     public void nextTurn() {
103:         this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.players.length;
104:         //System.out.println(String.format("next player: %s", this.players[currentPlayerIndex]));
105:     }
106: 
107:     @Override
108:     public IC4State deepCopy() {
109:         // we implemented a mutable state pattern
110:         // *never* did any of the sparse documentation of the gaming framework state
111:         // anywhere that the state should be immutable
112:         // that would have been nice to know that ahead of time :P
113:         // Anyway, it should work with immutable states as well now.
114:         return new C4State(this);
115:     }
116: 
117:     @Override
118:     public IC4Board getBoard() {
119:         return this.board;
120:     }
121: 
122:     @Override
123:     public IC4Player getCurrentPlayer() {
124:         return this.players[currentPlayerIndex];
125:     }
126: 
127:     @Override
128:     public void onMoveCompleted() {
129:         // quick pass scanning for *any* solution
130:         final Optional<IC4Solution> solution = this.board.tryFindFirstSolution();
131:         if (solution.isPresent()) {
132:             // second slower pass scanning for *all* solutions.
133:             // we allow 2^31 - 1 different players on a board of up to 2^31 - 1 fields with 
134:             // a valid solution length between 2 and 2^31-1 ¯\_(ツ)_/¯
135:             // if we allow for stuff like this, we should probably also handle changed rules
136:             // where multiple winners could be possible :P
137:             final Set<IC4Solution> solutions = this.board.findAllSolutions();
138:             this.gameOver(Optional.of(solutions));
139:         } else {
140:             final boolean isFull = this.board.isFull();
141:             if (isFull) {
142:                 this.gameOver(Optional.empty());
143:             }
144:         }
145:     }
146: 
147:     /**
148:      * Sets the state of all players to {@link PlayerState#LOST} except for the players that own at least 
149:      * one of the given {@code solutions}.
150:      * If no solutions are given, all players are set to {@link PlayerState#DRAW}.
151:      * @param optionalSolutions The solutions, if any.
152:      */
153:     private void gameOver(final Optional<Set<IC4Solution>> optionalSolutions) {
154:         if (optionalSolutions.isPresent()) {
155:             final Set<IC4Solution> solutions = optionalSolutions.get();
156:             for (final IC4Player player : this.players) {
157:                 // we can't just do
158:                 // sln.getOwner().setState(WON)
159:                 // here because players are apparently deep copied with every move... :/
160:                 // so yeah... Now we have this nightmare here:
161:                 if (solutions.stream().anyMatch(sln -> sln.getOwner().equals(player))) {
162:                     player.setState(PlayerState.WON);
163:                 } else {
164:                     player.setState(PlayerState.LOST);
165:                 }
166:             }
167:         } else {
168:             for (final IC4Player player : players) {
169:                 player.setState(PlayerState.DRAW);
170:             }
171:         }
172:     }
173: 
174:     @Override
175:     public String toString() {
176:         final StringBuilder builder = new StringBuilder(64);
177:         builder.append("C4State [board=").append(board.toString())
178:             .append(", players=").append(Arrays.toString(players))
179:             .append(", currentPlayer=").append(players[currentPlayerIndex].toString())
180:             .append(']');
181:         return builder.toString();
182:     }
183: }